# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
#     http://aws.amazon.com/apache2.0e
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import json

from botocore.compat import OrderedDict

from unittest import TestCase
from samcli.yamlhelper import yaml_parse, yaml_dump


class TestYaml(TestCase):
    yaml_with_tags = """
    Resource:
        Key1: !Ref Something
        Key2: !GetAtt Another.Arn
        Key3: !FooBar [!Baz YetAnother, "hello"]
        Key4: !SomeTag {"a": "1"}
        Key5: !GetAtt OneMore.Outputs.Arn
        Key6: !Condition OtherCondition
        Key7: "012345678"
    """

    parsed_yaml_dict = {
        "Resource": {
            "Key1": {"Ref": "Something"},
            "Key2": {"Fn::GetAtt": ["Another", "Arn"]},
            "Key3": {"Fn::FooBar": [{"Fn::Baz": "YetAnother"}, "hello"]},
            "Key4": {"Fn::SomeTag": {"a": "1"}},
            "Key5": {"Fn::GetAtt": ["OneMore", "Outputs.Arn"]},
            "Key6": {"Condition": "OtherCondition"},
            "Key7": "012345678",
        }
    }

    def test_yaml_with_tags(self):
        output = yaml_parse(self.yaml_with_tags)
        self.assertEqual(self.parsed_yaml_dict, output)

        # Make sure formatter and parser work well with each other
        formatted_str = yaml_dump(output)
        output_again = yaml_parse(formatted_str)
        self.assertEqual(output, output_again)

    def test_yaml_dumps(self):
        input_yaml_dict = {"Resource": {"Key7": "012345678"}}

        expected_output = "Resource:\n  Key7: '012345678'\n"

        output = yaml_dump(input_yaml_dict)

        self.assertEqual(output, expected_output)

    def test_yaml_getatt(self):
        # This is an invalid syntax for !GetAtt. But make sure the code does
        # not crash when we encounter this syntax. Let CloudFormation
        # interpret this value at runtime
        yaml_input = """
        Resource:
            Key: !GetAtt ["a", "b"]
        """

        output = {"Resource": {"Key": {"Fn::GetAtt": ["a", "b"]}}}

        actual_output = yaml_parse(yaml_input)
        self.assertEqual(actual_output, output)

    def test_parse_json_with_tabs(self):
        template = '{\n\t"foo": "bar"\n}'
        output = yaml_parse(template)
        self.assertEqual(output, {"foo": "bar"})

    def test_parse_json_preserve_elements_order(self):
        input_template = """
        {
            "B_Resource": {
                "Key2": {
                    "Name": "name2"
                },
                "Key1": {
                    "Name": "name1"
                }
            },
            "A_Resource": {
                "Key2": {
                    "Name": "name2"
                },
                "Key1": {
                    "Name": "name1"
                }
            }
        }
        """
        expected_dict = OrderedDict(
            [
                ("B_Resource", OrderedDict([("Key2", {"Name": "name2"}), ("Key1", {"Name": "name1"})])),
                ("A_Resource", OrderedDict([("Key2", {"Name": "name2"}), ("Key1", {"Name": "name1"})])),
            ]
        )
        output_dict = yaml_parse(input_template)
        self.assertEqual(expected_dict, output_dict)

    def test_parse_yaml_preserve_elements_order(self):
        input_template = (
            "B_Resource:\n"
            "  Key2:\n"
            "    Name: name2\n"
            "  Key1:\n"
            "    Name: name1\n"
            "A_Resource:\n"
            "  Key2:\n"
            "    Name: name2\n"
            "  Key1:\n"
            "    Name: name1\n"
        )
        output_dict = yaml_parse(input_template)
        expected_dict = OrderedDict(
            [
                ("B_Resource", OrderedDict([("Key2", {"Name": "name2"}), ("Key1", {"Name": "name1"})])),
                ("A_Resource", OrderedDict([("Key2", {"Name": "name2"}), ("Key1", {"Name": "name1"})])),
            ]
        )
        self.assertEqual(expected_dict, output_dict)

        output_template = yaml_dump(output_dict)
        self.assertEqual(input_template, output_template)

    def test_yaml_merge_tag(self):
        test_yaml = """
        base: &base
            property: value
        test:
            <<: *base
        """
        output = yaml_parse(test_yaml)
        self.assertTrue(isinstance(output, OrderedDict))
        self.assertEqual(output.get("test").get("property"), "value")

    def test_unroll_yaml_anchors(self):
        properties = {"Foo": "bar", "Spam": "eggs"}
        template = {"Resources": {"Resource1": {"Properties": properties}, "Resource2": {"Properties": properties}}}

        expected = (
            "Resources:\n"
            "  Resource1:\n"
            "    Properties:\n"
            "      Foo: bar\n"
            "      Spam: eggs\n"
            "  Resource2:\n"
            "    Properties:\n"
            "      Foo: bar\n"
            "      Spam: eggs\n"
        )
        actual = yaml_dump(template)
        self.assertEqual(actual, expected)

    def test_unquoted_template_format_version_to_json(self):
        input_template = (
            "AWSTemplateFormatVersion: 2010-09-09\n"
            "Transform: AWS::Serverless-2016-10-31\n"
            "Resources:\n"
            "  HelloWorldFunction:\n"
            "    Type: AWS::Serverless::Function\n"
            "    Properties:\n"
            "      Handler: main.handler\n"
            "      Runtime: python3.12\n"
            "      CodeUri: .\n"
            "      Timeout: 600\n"
        )

        output = yaml_parse(input_template)
        self.assertIsInstance(output["AWSTemplateFormatVersion"], str)
        self.assertEqual(output["AWSTemplateFormatVersion"], "2010-09-09")
        # Raises a `TypeError` if an unquoted `AWSTemplateFormatVersion` value has been parsed to a
        # `datetime` object and not a string by `yaml_parse` when using `--use-json` argument.
        json.dumps(output)
